package com.jvm.simulation.hotspot.bytecode;

import com.jvm.simulation.hotspot.*;
import com.jvm.simulation.hotspot.descriptor.DescriptorStream2;
import com.jvm.simulation.hotspot.memory.StackObj;
import com.jvm.simulation.hotspot.runtime.JavaVFrame;
import com.jvm.simulation.hotspot.runtime.StackValue;
import com.jvm.simulation.hotspot.runtime.VFrame;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.swing.plaf.synth.SynthOptionPaneUI;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class BytecodeInterpreter
        extends StackObj
{

    private static Logger logger = LoggerFactory.getLogger(BytecodeInterpreter.class);

    public static void run(JavaThread thread, MethodInfo method) {
        // 得到字节码指令
        BytecodeStream code = method.getCodeAttributes()[0].getCode();

        // 得到栈帧
        JavaVFrame frame = (JavaVFrame) thread.getStack().peek();

        int c;

        while (!code.end()) {
            c = code.getU1Code();

            switch (c) {
                case Bytecodes.ICONST_0: {
                    logger.info("执行指令: ICONST_0");

                    frame.getStack().push(new StackValue(BasicType.T_INT, 0));

                    break;
                }
                case Bytecodes.ICONST_1: {
                    logger.info("执行指令: ICONST_1");

                    frame.getStack().push(new StackValue(BasicType.T_INT, 1));

                    break;
                }
                case Bytecodes.ICONST_2: {
                    logger.info("执行指令: ICONST_2");

                    frame.getStack().push(new StackValue(BasicType.T_INT, 2));

                    break;
                }
                case Bytecodes.ICONST_3: {
                    logger.info("执行指令: ICONST_3");

                    frame.getStack().push(new StackValue(BasicType.T_INT, 3));

                    break;
                }
                case Bytecodes.ICONST_4: {
                    logger.info("执行指令: ICONST_4");

                    frame.getStack().push(new StackValue(BasicType.T_INT, 4));

                    break;
                }
                case Bytecodes.ICONST_5: {
                    logger.info("执行指令: ICONST_5");

                    frame.getStack().push(new StackValue(BasicType.T_INT, 5));

                    break;
                }
                case Bytecodes.FCONST_0: {
                    logger.info("执行指令: FCONST_0");

                    frame.getStack().push(new StackValue(BasicType.T_FLOAT, 0f));

                    break;
                }
                case Bytecodes.FCONST_1: {
                    logger.info("执行指令: FCONST_1");

                    frame.getStack().push(new StackValue(BasicType.T_FLOAT, 1f));

                    break;
                }
                case Bytecodes.FCONST_2: {
                    logger.info("执行指令: FCONST_2");

                    frame.getStack().push(new StackValue(BasicType.T_FLOAT, 2f));

                    break;
                }

                case Bytecodes.LCONST_0:{
                    logger.info("执行指令: LCONST_0");

                    frame.getStack().push(new StackValue(BasicType.T_LONG, (long)0));

                    break;

                }

                case Bytecodes.LCONST_1:{
                    logger.info("执行指令: LCONST_1");

                    frame.getStack().push(new StackValue(BasicType.T_LONG, (long)1));

                    break;

                }
                case Bytecodes.BIPUSH: {
                    logger.info("执行指令: BIPUSH");

                    // 获取操作数
                    int val = code.getU1Code();

                    // 操作数入栈（操作数栈）
                    frame.getStack().push(new StackValue(BasicType.T_INT, val));

                    break;
                }
                case Bytecodes.LDC: {
                    logger.info("执行指令: LDC");

                    // 取出操作数
                    int operand = code.getU1Code();

                    // 取出常量池中的信息
                    int tag = method.getKlass().getContantPool().getTag()[operand];
                    switch (tag) {
                        case ClassConstants.JVM_CONSTANT_Float: {
                            // 取出数值
                            float f = (float) method.getKlass().getContantPool().getContantPool().get(operand);

                            // 压入栈
                            frame.getStack().push(new StackValue(BasicType.T_FLOAT, f));

                            break;
                        }
                        case ClassConstants.JVM_CONSTANT_String: {
                            int index = (int) method.getKlass().getContantPool().getContantPool().get(operand);

                            String content = (String) method.getKlass().getContantPool().getContantPool().get(index);

                            frame.getStack().push(new StackValue(BasicType.T_OBJECT, content));

                            break;
                        }
                        case ClassConstants.JVM_CONSTANT_Class: {
                            break;
                        }
                        default: {
                            logger.error("未知类型");

                            break;
                        }
                    }

                    break;
                }
                /**
                 * 将常量池中的long、doubel类型的数值推送至栈顶
                 *
                 * 我的JVM因为在解析常量池中合了，所以读一次就可以了
                 * Hotspot中解析常量池的时候没合，会读两次，push两次
                 */
                case Bytecodes.LDC2_W: {
                    logger.info("执行指令: LDC2_W");

                    // 取出操作数
                    int operand = code.getUnsignedShort();

                    /**
                     * 数值入栈，这边实现方式略有差别
                     *      long是用8字节的byte数组存储的
                     *      double是用两个slot存储的
                     */
                    int tag = method.getKlass().getContantPool().getTag()[operand];

                    if (ClassConstants.JVM_CONSTANT_Long == tag) {
                        long l = (long) method.getKlass().getContantPool().getContantPool().get(operand);
                        frame.getStack().push(new StackValue(BasicType.T_LONG, l));
                    } else if (ClassConstants.JVM_CONSTANT_Double == tag) {
                        double d = (double) method.getKlass().getContantPool().getContantPool().get(operand);
                        frame.getStack().pushDouble(d);
                    } else {
                        throw new Error("无法识别的格式");
                    }

                    break;
                }
                case Bytecodes.ILOAD_0: {
                    logger.info("执行指令: ILOAD_0");

                    // 取出局部变量表中的数据
                    StackValue value = frame.getLocals().get(0);

                    // 压入栈
                    frame.getStack().push(value);

                    break;
                }
                case Bytecodes.ILOAD_1: {
                    logger.info("执行指令: ILOAD_1");

                    // 取出局部变量表中的数据
                    StackValue value = frame.getLocals().get(1);

                    // 压入栈
                    frame.getStack().push(value);

                    break;
                }
                case Bytecodes.ILOAD_2: {
                    logger.info("执行指令: ILOAD_2");

                    // 取出局部变量表中的数据
                    StackValue value = frame.getLocals().get(2);

                    // 压入栈
                    frame.getStack().push(value);

                    break;
                }
                case Bytecodes.ILOAD_3: {
                    logger.info("执行指令: ILOAD_3");

                    // 取出局部变量表中的数据
                    StackValue value = frame.getLocals().get(3);

                    // 压入栈
                    frame.getStack().push(value);

                    break;
                }
                case Bytecodes.LLOAD_0: {
                    logger.info("执行指令: LLOAD_0");

                    // 取出局部变量表中的数据
                    StackValue value = frame.getLocals().get(0);

                    // 压入栈
                    frame.getStack().push(value);

                    break;
                }
                case Bytecodes.LLOAD_1: {
                    logger.info("执行指令: LLOAD_1");

                    // 取出局部变量表中的数据
                    StackValue value = frame.getLocals().get(1);

                    // 压入栈
                    frame.getStack().push(value);

                    break;
                }
                case Bytecodes.LLOAD_2: {
                    logger.info("执行指令: LLOAD_2");

                    // 取出局部变量表中的数据
                    StackValue value = frame.getLocals().get(2);

                    // 压入栈
                    frame.getStack().push(value);

                    break;
                }
                case Bytecodes.LLOAD_3: {
                    logger.info("执行指令: LLOAD_3");

                    // 取出局部变量表中的数据
                    StackValue value = frame.getLocals().get(3);

                    // 压入栈
                    frame.getStack().push(value);

                    break;
                }
                case Bytecodes.FLOAD_0: {
                    logger.info("执行指令: FLOAD_0");

                    // 取出局部变量表中的数据
                    StackValue value = frame.getLocals().get(0);

                    // 压入栈
                    frame.getStack().push(value);

                    break;
                }
                case Bytecodes.FLOAD_1: {
                    logger.info("执行指令: FLOAD_1");

                    // 取出局部变量表中的数据
                    StackValue value = frame.getLocals().get(1);

                    // 压入栈
                    frame.getStack().push(value);

                    break;
                }
                case Bytecodes.FLOAD_2: {
                    logger.info("执行指令: FLOAD_2");

                    // 取出局部变量表中的数据
                    StackValue value = frame.getLocals().get(2);

                    // 压入栈
                    frame.getStack().push(value);

                    break;
                }
                case Bytecodes.FLOAD_3: {
                    logger.info("执行指令: FLOAD_3");

                    // 取出局部变量表中的数据
                    StackValue value = frame.getLocals().get(3);

                    // 压入栈
                    frame.getStack().push(value);

                    break;
                }
                case Bytecodes.DLOAD_0: {
                    logger.info("执行指令: DLOAD_0");

                    // 取出数据
                    StackValue value1 = frame.getLocals().get(0);
                    StackValue value2 = frame.getLocals().get(1);

                    // 压入栈
                    frame.getStack().push(value2);
                    frame.getStack().push(value1);

                    break;
                }
                case Bytecodes.DLOAD_1: {
                    logger.info("执行指令: DLOAD_1");

                    // 取出数据
                    StackValue value1 = frame.getLocals().get(1);
                    StackValue value2 = frame.getLocals().get(2);

                    // 压入栈
                    frame.getStack().push(value2);
                    frame.getStack().push(value1);

                    break;
                }
                case Bytecodes.DLOAD_2: {
                    logger.info("执行指令: DLOAD_2");

                    // 取出数据
                    StackValue value1 = frame.getLocals().get(2);
                    StackValue value2 = frame.getLocals().get(3);

                    // 压入栈
                    frame.getStack().push(value2);
                    frame.getStack().push(value1);

                    break;
                }
                case Bytecodes.DLOAD_3: {
                    logger.info("执行指令: DLOAD_3");

                    // 取出数据
                    StackValue value1 = frame.getLocals().get(3);
                    StackValue value2 = frame.getLocals().get(4);

                    // 压入栈
                    frame.getStack().push(value2);
                    frame.getStack().push(value1);

                    break;
                }
                case Bytecodes.ALOAD_0:{
                    StackValue stackValue = frame.getLocals().get(0);
                    frame.getStack().push(stackValue);
                    break;
                }
                case Bytecodes.ILOAD: {
                    logger.info("执行指令: ILOAD");

                    int val = code.getU1Code();

                    StackValue value = frame.getLocals().get(val);

                    frame.getStack().push(value);

                    break;
                }
                case Bytecodes.ISTORE: {
                    logger.info("执行指令: ISTORE");

                    // 获取操作数
                    int index = code.getU1Code();

                    // 取出数据
                    StackValue values = frame.getStack().pop();

                    // 存入局部变量表
                    frame.getLocals().add(index, values);

                    break;
                }
                case Bytecodes.ISTORE_0: {
                    logger.info("执行指令: ISTORE_0");

                    // 取出栈顶元素
                    StackValue value = frame.getStack().pop();

                    // 存入局部变量表
                    frame.getLocals().add(0, value);

                    break;
                }
                case Bytecodes.ISTORE_1: {
                    logger.info("执行指令: ISTORE_1");

                    // 取出栈顶元素
                    StackValue value = frame.getStack().pop();

                    // 存入局部变量表
                    frame.getLocals().add(1, value);

                    break;
                }
                case Bytecodes.ISTORE_2: {
                    logger.info("执行指令: ISTORE_2");

                    // 取出栈顶元素
                    StackValue value = frame.getStack().pop();

                    // 存入局部变量表
                    frame.getLocals().add(2, value);

                    break;
                }
                case Bytecodes.ISTORE_3: {
                    logger.info("执行指令: ISTORE_3");

                    // 取出栈顶元素
                    StackValue value = frame.getStack().pop();

                    // 存入局部变量表
                    frame.getLocals().add(3, value);

                    break;
                }
                case Bytecodes.IINC:{
                    int byteIdx = code.getU1Code();
                    int constbyte = code.getU1Code();

                    frame.getLocals().add(byteIdx,new StackValue(BasicType.T_INT,((int)frame.getLocals().get(byteIdx).getData()) + constbyte));
                    break;
                }
                case Bytecodes.LSTORE: {
                    int byteIdx = code.getU1Code();
                    StackValue pop = frame.getStack().pop();
                    frame.getLocals().add(byteIdx,pop);
                    break;
                }
                case Bytecodes.LSTORE_0: {
                    logger.info("执行指令: LSTORE_0");

                    // 取出栈顶元素
                    StackValue value = frame.getStack().pop();

                    // 存入局部变量表
                    frame.getLocals().add(0, value);

                    break;
                }
                case Bytecodes.LSTORE_1: {
                    logger.info("执行指令: LSTORE_1");

                    // 取出栈顶元素
                    StackValue value = frame.getStack().pop();

                    // 存入局部变量表
                    frame.getLocals().add(1, value);

                    break;
                }
                case Bytecodes.LSTORE_2: {
                    logger.info("执行指令: LSTORE_2");

                    // 取出栈顶元素
                    StackValue value = frame.getStack().pop();

                    // 存入局部变量表
                    frame.getLocals().add(2, value);

                    break;
                }
                case Bytecodes.LSTORE_3: {
                    logger.info("执行指令: LSTORE_3");

                    // 取出栈顶元素
                    StackValue value = frame.getStack().pop();

                    // 存入局部变量表
                    frame.getLocals().add(3, value);

                    break;
                }
                case Bytecodes.LLOAD:{
                    int byteIdx = code.getU1Code();
                    StackValue stackValue = frame.getLocals().get(byteIdx);
                    frame.getStack().push(stackValue);
                    break;
                }
                case Bytecodes.LADD:{

                    StackValue pop = frame.getStack().pop();
                    long l = (long)pop.getData();
                    l +=  (long)frame.getStack().pop().getData();
                    frame.getStack().push(new StackValue(BasicType.T_LONG,l));
                    break;
                }
                case Bytecodes.FSTORE_0: {
                    logger.info("执行指令: FSTORE_0");

                    // 取出栈顶元素
                    StackValue value = frame.getStack().pop();

                    // 存入局部变量表
                    frame.getLocals().add(0, value);

                    break;
                }
                case Bytecodes.FSTORE_1: {
                    logger.info("执行指令: FSTORE_1");

                    // 取出栈顶元素
                    StackValue value = frame.getStack().pop();

                    // 存入局部变量表
                    frame.getLocals().add(1, value);

                    break;
                }
                case Bytecodes.FSTORE_2: {
                    logger.info("执行指令: FSTORE_2");

                    // 取出栈顶元素
                    StackValue value = frame.getStack().pop();

                    // 存入局部变量表
                    frame.getLocals().add(2, value);

                    break;
                }
                case Bytecodes.FSTORE_3: {
                    logger.info("执行指令: FSTORE_3");

                    // 取出栈顶元素
                    StackValue value = frame.getStack().pop();

                    // 存入局部变量表
                    frame.getLocals().add(3, value);

                    break;
                }
                case Bytecodes.DSTORE_0: {
                    logger.info("执行指令: DSTORE_0");

                    // 取出数据
                    StackValue[] values = frame.getStack().popDouble2();

                    // 存入局部变量表
                    frame.getLocals().add(0, values[0]);
                    frame.getLocals().add(1, values[1]);

                    break;
                }
                case Bytecodes.DSTORE_1: {
                    logger.info("执行指令: DSTORE_1");

                    // 取出数据
                    StackValue[] values = frame.getStack().popDouble2();

                    // 存入局部变量表
                    frame.getLocals().add(1, values[0]);
                    frame.getLocals().add(2, values[1]);

                    break;
                }
                case Bytecodes.DSTORE_2: {
                    logger.info("执行指令: DSTORE_2");

                    // 取出数据
                    StackValue[] values = frame.getStack().popDouble2();

                    // 存入局部变量表
                    frame.getLocals().add(2, values[0]);
                    frame.getLocals().add(3, values[1]);

                    break;
                }
                case Bytecodes.DSTORE_3: {
                    logger.info("执行指令: DSTORE_3");

                    // 取出数据
                    StackValue[] values = frame.getStack().popDouble2();

                    // 存入局部变量表
                    frame.getLocals().add(3, values[0]);
                    frame.getLocals().add(4, values[1]);

                    break;
                }
                case Bytecodes.POP:{
                    frame.getStack().pop();
                    break;
                }
                case Bytecodes.DUP:{
                    frame.getStack().push(frame.getStack().peek());  //todo double或者long类型验证
                    break;
                }
                case Bytecodes.DUP2:{
                    frame.getStack().push(frame.getStack().peek());  //todo double或者long类型验证
                    break;
                }

                case Bytecodes.RETURN: {
                    logger.info("执行指令: RETURN");

                    // pop出栈帧
                    thread.getStack().pop();

                    logger.info("\t 剩余栈帧数量: " + thread.getStack().size());

                    break;
                }
                case Bytecodes.LRETURN:{
                    System.out.println();
                    StackValue pop = frame.getStack().pop();
                    thread.getStack().pop();

                    logger.info("\t 剩余栈帧数量: " + thread.getStack().size());

                    // 将返回值压人调用者的栈
                    ((JavaVFrame) thread.getStack().peek()).getStack().push(pop);
                    break;
                }
                case Bytecodes.GETSTATIC: {
                    logger.info("执行指令: GETSTATIC");

                    // 获取操作数
                    short operand = code.getUnsignedShort();

                    try {
                        Class<?> clazz = Class.forName(getClassName(method, operand).replace('/', '.'));

                        Field field = clazz.getField(getFiledName(method, operand));

                        frame.getStack().push(new StackValue(BasicType.T_OBJECT, field.get(null)));
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    } catch (NoSuchFieldException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }

                    break;
                }
                case Bytecodes.ISHL :{
                    StackValue pop = frame.getStack().pop();
                    int iconst = (int) pop.getData();

                    frame.getStack().push(new StackValue(BasicType.T_INT, (int) frame.getStack().pop().getData() << iconst));
                    break;
                }

                /**
                 * 调用虚方法
                 * 1、public修饰
                 * 2、protected修饰
                 */
                case Bytecodes.INVOKEVIRTUAL: {
                    logger.info("执行指令: INVOKEVIRTUAL");

                    // 取出操作数
                    short operand = code.getUnsignedShort();

                    // 获取类名、方法名、方法描述符
                    String className = getClassName(method, operand);
                    String methodName = getFiledName(method, operand);
                    String descriptorName = getDescriptorName(method, operand);

                    /**
                     * 判断是系统类还是自定义的类
                     *  系统类走反射
                     *  自定义的类自己处理
                     */
                    if (className.startsWith("java")) {
                        DescriptorStream2 descriptorStream = new DescriptorStream2(descriptorName);
                        descriptorStream.parseMethod();

                        Object[] params = descriptorStream.getParamsVal(frame);
                        Class[] paramsClass = descriptorStream.getParamsType();

                        Object obj = frame.getStack().pop().getObject();

                        try {
                            Method fun = obj.getClass().getMethod(methodName, paramsClass);

                            /**
                             * 处理：
                             *  1、无返回值
                             *  2、有返回值
                             */
                            if (BasicType.T_VOID == descriptorStream.getReturnElement().getType()) {
                                fun.invoke(obj, params);
                            } else {
                                descriptorStream.pushField(fun.invoke(obj, params), frame);
                            }

                        } catch (NoSuchMethodException e) {
                            e.printStackTrace();
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        } catch (InvocationTargetException e) {
                            e.printStackTrace();
                        }
                    } else {

                    }

                    break;
                }
                case Bytecodes.INVOKESTATIC:{
                    // 取出操作数
                    short operand = code.getUnsignedShort();
                    String className = getClassName(method, operand);
                    String methodName = getFiledName(method, operand);
                    String descriptorName = getDescriptorName(method, operand);
                    DescriptorStream2 descriptorStream = new DescriptorStream2(descriptorName);

                    try {
                        if (className.startsWith("java")) {
                            runJavaClassMethod(method, frame, operand, methodName, descriptorStream);
                        } else {
                            for (MethodInfo methodInfo : method.getKlass().getMethod_info()) {
                                if(methodInfo.getMethodName().equals(methodName) && methodInfo.getDescriptor().equals(descriptorName)){
                                    methodInfo.setDescriptorStream(descriptorStream);
                                    // 创建栈帧
                                    JavaVFrame newFrame = new JavaVFrame(methodInfo.getCodeAttributes()[0].getMaxLocals(), method);

                                    for (int i = 0; i < methodInfo.getCodeAttributes()[0].getMaxLocals(); i++) {

                                        newFrame.getLocals().add(i,((JavaVFrame) thread.getStack().peek()).getStack().pop());

                                    }

                                    thread.getStack().push( thread.getStack().pop());
                                    thread.getStack().push(newFrame);
                                    logger.info("第 " + thread.getStack().size() + " 个栈帧");

                                    // 执行任务交给字节码解释器

                                    LoadClassFile.runMethod(
                                            LoadClassFile.findLoadedKlass(className),
                                            methodName,
                                            descriptorName
                                            );
                                }
                            }

                        }
                    } catch (NoSuchMethodException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                    break;
                }case Bytecodes.INVOKEDYNAMIC:{
                    int operand = code.getUnsignedShort();
                    int idx = (int)method.getKlass().getContantPool().get(operand);
                    int classNameAndNameAndTypeIdx = (int) method.getKlass().getContantPool().get(idx);
                    String methodName =  (String) method.getKlass().getContantPool().get(classNameAndNameAndTypeIdx>>16);
                    String nameAndTypeIndex = (String) method.getKlass().getContantPool().get(classNameAndNameAndTypeIdx & 0xff);
                    operand = code.getU1Code();
//                    method.getKlass().getAttribute_info()[]




                    System.out.println("");
                    break;
                }
                default: {
                    throw new Error("无效指令:" + c);
                }
            } /* end switch */
        } /* end while */
    }

    private static void runJavaClassMethod(MethodInfo method, JavaVFrame frame, short operand, String methodName, DescriptorStream2 descriptorStream) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        descriptorStream.parseMethod();
        Object[] params = descriptorStream.getParamsVal(frame);
        Class[] paramsClass = descriptorStream.getParamsType();

        Class<?> clazz = Class.forName(getClassName(method, operand).replace('/', '.'));

        Method fun = clazz.getMethod(methodName, paramsClass);
        /**
         * 处理：
         *  1、无返回值
         *  2、有返回值
         */
        if (BasicType.T_VOID == descriptorStream.getReturnElement().getType()) {
            fun.invoke(clazz, params);
        } else {
            descriptorStream.pushField(fun.invoke(clazz, params), frame);
        }
    }

    private static String getClassName(MethodInfo method, int operand) {
        int data = (int) method.getKlass().getContantPool().getContantPool().get(operand);
        int classIdx = (int) method.getKlass().getContantPool().getContantPool().get(data>>16);
        return (String)  method.getKlass().getContantPool().getContantPool().get(classIdx);
    }

    private static String getFiledName(MethodInfo method, int operand) {
        int data = (int) method.getKlass().getContantPool().getContantPool().get(operand);
        int fieldIdx = data & 0xFF;
        fieldIdx = (int) method.getKlass().getContantPool().getContantPool().get(fieldIdx);

        fieldIdx = fieldIdx>>16;
        return (String) method.getKlass().getContantPool().getContantPool().get(fieldIdx);
    }

    private static String getDescriptorName(MethodInfo method, int operand) {
        int data = (int) method.getKlass().getContantPool().getContantPool().get(operand);
        int descriptor = data & 0xFF;
        descriptor = (int) method.getKlass().getContantPool().getContantPool().get(descriptor);

        descriptor = descriptor & 0xFF;
        return (String) method.getKlass().getContantPool().getContantPool().get(descriptor);
    }

}